package hyl.ext.ws;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.Set;
import javax.websocket.Session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

import hyl.core.MyFun;
import hyl.core.run.MyRun;
import hyl.ext.base.MySession;
import hyl.ext.base.SessionFactory;
import hyl.ext.ws.msg.WsMsgS;

/**
 * ws 三层模型
 * 
 * 适用于 用户的消息服务 不适合终端 <br>
 * 
 * 一个用户可以登录多个终端,也就是一个用户账号 即可登录手机也可登录电脑 ,两个终端都可以接收到数据<br>
 * 
 * 需要先登录授权,然后才能发送消息<br>
 * 
 * 主要是通过主题模型调度消息
 * 
 * @author 37798955@qq.com
 *
 */
public class WsServe3 extends WsServe {
	public static final Logger Log = LoggerFactory.getLogger(WsServe3.class);
	// 静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
	// protected static AtomicInteger onlineCount = new AtomicInteger(0);

	/**
	 * 用户 == 组
	 */
	protected WsRoomMen _room = null;
	// static MyCert _密钥对 = MyCert.getInstance();
	// static {
	// _密钥对.load密钥文本文件("./cert");
	// }
	// private static volatile boolean

	/**
	 * 连接建立成功后 调用的方法 onOpen 然后 返回 公钥
	 * 
	 * 需要登录
	 * 
	 * @param session 可选的参数。session为与某个客户端的连接会话，需要通过它来给客户端发送数据
	 */

	/**
	 * 不需要登录只要有groupid 即可
	 * 
	 * @param groupid
	 * @param session
	 */
	public void loginById(String 分组id, Session session) {
		_Ws会话 = session;
		_clientid = 分组id + '_' + session.getId();
		login(分组id);
	}

	void login(String gid) {

		WsRoom wc = _在线房间集.get(gid);
		if (wc == null) {
			_room = new WsRoomMen(gid,"三");
			_在线房间集.put(gid, _room);
			// 发送历史消息
			List<WsMsgS> list = _room.loadMsgs();
			if (list != null) {
				for (WsMsgS wm : list) {
					sendTo(wm);
				}
			}
			_room.add(this);
		} else {
			if (!WsRoom.D三层.equals(wc._模型)) {
				_room = (WsRoomMen) wc;
				_room.add(this);
			} else {
				sendTo(gid + "编号已被双层模型中的客户端使用了");
				return;
			}
		}
		_在线终端集.put(_clientid, this);
	}

	/**
	 * 连接关闭调用的方法
	 */
	public void onClose() {
		if (_room == null)
			return;
		_room.remove(this);
		if (_clientid == null)
			return;
		_在线终端集.remove(_clientid);
	}

//////////////////////////消息处理入口//////////////////
	/**
	 * 收到客户端消息后调用的方法
	 * 
	 * @param message 客户端发送过来的消息
	 * @param session 两次连接的session 是不同的,不断线的情况下 连接是一个
	 * @throws Exception
	 */
	protected void onMessage(String message, Session session) {
		JSONObject jot = JSON.parseObject(message);
		String cmd = jot.getString("type");
		// if (cmd.equals("主题"))
		// on主题事件(jot.getString("cmd"), jot.getString("param"));
		// if (cmd.equals("消息"))
		// on消息事件(jot.getString("cmd"), jot.getString("to"), jot.getString("text"));
		if (cmd.equals("登录"))
			loginByToken(jot.getString("token"));

	}
	
	public void onMessage(byte[] 消息, Session session) {
		
	}
	// MyKryo mKryo=MyKryo.getInstance();

	////////////////////////// 基础事件//////////////////

	/**
	 * 登录以后
	 * 
	 * 才会分配用户id
	 * 
	 * 才会加入 会话集
	 * 
	 * @param 密文
	 */
	public void loginByToken(String token) {
		// String 密文
		// byte[] bytes = MyBase64.decode(密文);
		// String token = MyFun.bytes2Str(_密钥对.f私钥解密(bytes), AIni.charset);
		if (MyFun.isEmpty(token)) {
			close();
			return;
		}
		// 从会话中查询 token
		MySession ms = SessionFactory.getSessionById(token);
		if (ms == null) {
			close();
			return;
		}
		// 登录成功
		String usid = String.valueOf(ms.getUserId());
		login(usid);
	}

	////////////////////////////////////////
	// 发送 文本时如果不成功 要保存到本地文件
	// 等待用户上线时再发送
	// 消息分为 下发和内部流转
	////////////////////////////////////////////////
//	public String on消息事件(String 消息) {
//		CMsg o = CMsg.getInstance(消息);
//		if (o.s主题.equals("消息")) {
//			if ("个人".equals(o.s键)) {
//				return WsServe3.sendTo(o.s键, o.getS内容());
//			} else if ("主题".equals(o.s键)) {
//				return WsServe3.sendTopic(o.s键, o.getS内容());
//			} else if ("广播".equals(o.s键)) {
//				return WsServe3.broadcast(o.getS内容());
//			}
//		}
//		return WsUtil.无处理;
//	}

	/**
	 * @param 指令 仅限 订阅和退订指令
	 * @param 参数
	 * @return
	 */
	public String h主题事件(String 指令, String 参数) {
		if (指令.equals("订阅")) {
			_主题管理器.f订阅主题(_room.RID, 参数);
		} else if (指令.equals("退订")) {
			_主题管理器.f取消订阅(_room.RID, 参数);
		}
		return WsUtil.成功;
	}

	//////////////////////////////// 文本消息处理////////////////////
	/**
	 * 
	 * @param 消息
	 * @param 接收方
	 * @param 主题
	 * @param 内容
	 */
	protected static void sendMsg(String 接收方, String 主题, String 内容) {
		// 客户 可以是 群组 也可以是用户
		WsRoom wc = _在线房间集.get(接收方);
		if (wc == null) {// 如果客户不在缓存中就立即创建一个
			wc = new WsRoomMen(接收方);
			_在线房间集.put(接收方, wc);
		}
		if (wc instanceof WsRoomMen) {
			WsRoomMen wg = (WsRoomMen) wc;
			WsMsgS 消息 = WsMsgS.getInstance(null);
			消息.setMsg(接收方, 主题, 内容);
			if (wg._在线终端 == null) { // 如果客户不在线
				wg.saveMsgs(消息);
			} else {
				// 否则发送到
				wg._在线终端.forEach((w) -> {
					w.sendTo(消息);
				});
			}
		}
	}

	public static String sendTo(String 接收人, String 内容) {
		if (MyFun.isEmpty(内容))
			return WsUtil.er内容不能为空;
		if (MyFun.isEmpty(接收人))
			return WsUtil.er接收人不能为空;

		sendMsg(接收人, null, 内容);
		return WsUtil.成功;
	}

	// 发送文本时需要缓存
	public static String sendTopic(String 主题, String 内容) {
		if (MyFun.isEmpty(内容))
			return WsUtil.er内容不能为空;
		if (MyFun.isEmpty(主题))
			return WsUtil.er主题不能为空;

		Set<String> 订阅人集 = _主题管理器.get某主题的订阅人(主题);
		if (订阅人集 == null || 订阅人集.isEmpty())
			return 主题 + ":该主题缺少订阅人";
		MyRun.start用户线程(() -> {
			for (String 订阅人 : 订阅人集) {
				// 原来为什么不用多线程呢?
				sendMsg(订阅人, 主题, 内容);

			}
		});
		return WsUtil.成功;
	}

////////////////////////////////字节流消息处理////////////////////
	////////////////////////////////// 以下是 推流场景 .发送不成功,不缓存数据
	////////////////////////////////// 例如直播////////////////////
	/**
	 * 适用于视频流 数据流
	 * 
	 * 字节流数据不缓存到本地文件目录
	 * 
	 * @param 会话id
	 * @param 内容
	 * @return
	 */
	public static String sendTo(String 接收人, byte[] 内容) {
		if (内容 == null || 内容.length == 0)
			return WsUtil.er内容不能为空;
		return sendTo(接收人, ByteBuffer.wrap(内容));
	}

	/**
	 * 适用于视频流 数据流
	 * 
	 * 字节流数据不缓存到本地文件目录
	 * 
	 * @param 会话id
	 * @param 内容
	 * @return
	 */
	public static String sendTo(String 接收人, ByteBuffer 内容) {
		if (内容 == null || 内容.remaining() == 0)
			return WsUtil.er内容不能为空;
		if (接收人 == null || 接收人.isEmpty())
			return WsUtil.er接收人不能为空;
		WsRoomMen ws = (WsRoomMen) _在线房间集.get(接收人);
		if (ws != null && ws._在线终端 != null) {
			ws._在线终端.forEach((w) -> {
				w.sendTo(内容);
			});
		}
		return WsUtil.成功;
	}

	/**
	 * 发送字节数据时 不缓存
	 * 
	 * @param 主题
	 * @param 内容
	 * @return
	 */
	public static String sendTopic(String 主题, byte[] 内容) {
		if (内容 == null)
			return WsUtil.er内容不能为空;
		return sendTopic(主题, ByteBuffer.wrap(内容));
	}

	/**
	 * 发送字节数据时 不缓存 ,如果需要缓存一定是特殊场景 继承后重新实现
	 * 
	 * @param 主题
	 * @param 内容
	 * @param 线程池 可以为空 非必填 适用于长字节数组 发送
	 * @return
	 */
	public static String sendTopic(String 主题, ByteBuffer 内容) {
		if (内容 == null || 内容.remaining() == 0)
			return WsUtil.er内容不能为空;
		if (MyFun.isEmpty(主题))
			return WsUtil.er主题不能为空;
		Set<String> 订阅人集 = _主题管理器.get某主题的订阅人(主题);
		if (订阅人集 == null)
			return 主题 + "主题没有订阅人";
		MyRun.start用户线程(() -> {
			for (String 订阅人 : 订阅人集) {
				sendTo(订阅人, 内容);
			}
		});
		return WsUtil.成功;
	}

	public static void closeAll() {
		_在线终端集.forEach((k, v) -> v.close());

	}
}
